﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Nonocast.Scheduling {
	public class SchedulerImpl<T> : Scheduler<T> {
		public event EventHandler TaskEvent;
		public Schedule<T> Schedule { get; protected set; }

		public SchedulerImpl() { }
		public SchedulerImpl(Schedule<T> schedule) {
			this.Schedule = schedule;
		}

		public void Run() {
			if (Schedule == null) throw new ArgumentNullException();
			worker = new Thread(new ThreadStart(InternalRun));
			worker.Start();
		}

		public void Close() {
			if (worker == null) return;
			signalEvent.Set();
			worker.Join();
		}

		private void InternalRun() {
			pendingTasks = CollectPendingTask();

			while (pendingTasks.Count > 0) {
				var current = pendingTasks.First();
				if (current.StartTime > DateTime.Now) {
					var timeout = (current.StartTime - DateTime.Now).TotalSeconds;
					Console.WriteLine("--- wait {0:F}s until {1}", timeout, current.StartTime);
					if (signalEvent.WaitOne(TimeSpan.FromSeconds(timeout))) break;
				}

				try {
					Fire(current);
				} catch (Exception ex) {
					Console.WriteLine("--- exception in fire: {0}", ex.Message);
				} finally {
					pendingTasks.Remove(current);

					try {
						var next = current.GeneratedBy.Generate(current);
						if (next != null) {
							pendingTasks.Add(next);
							pendingTasks = pendingTasks.OrderBy(p => p.StartTime).ToList();
						}
					} catch (Exception ex) {
						Console.WriteLine(ex.Message);
						// ignore
					}
				}
			}
		}

		private void Fire(Task<T> current) {
			if (TaskEvent != null) { TaskEvent(this, new TaskEventArgs<T>(current)); }
		}


		private List<Task<T>> CollectPendingTask() {
			return Schedule.Where(p => p.Generate() != null)
				.Select(p => p.Generate()).OrderBy(p => p.StartTime).ToList();
		}

		private List<Task<T>> pendingTasks;
		private Thread worker;
		private ManualResetEvent signalEvent = new ManualResetEvent(false);
	}

	public class TaskEventArgs<T> : EventArgs {
		public Task<T> Task { get; private set; }
		public TaskEventArgs(Task<T> arg) {
			this.Task = arg;
		}
	}
}
